package cn.kivensoft.xlsx.writer;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import cn.kivensoft.xlsx.writer.Style.BasicStyles;

public class Workbook {
	/**
	 * 当前选择的工作表
	 */
	private int selectedWorksheet;
	/**
	 * 获取当前工作表
	 */
	private Worksheet currentWorksheet;
	/**
	 * 所有工作表
	 */
	private List<Worksheet> worksheets = new ArrayList<Worksheet>();
	/**
	 * 无参数保存时的文件名
	 */
	private String fileName;
	/**
	 * 工作簿中的所有样式
	 */
	private List<Style> styles = new ArrayList<Style>();
	/**
	 * 工作薄相关信息，创建时间，作者之类的
	 */
	private Metadata workbookMetadata = new Metadata();
	/**
	 * 工作薄是否保护标志
	 */
	private boolean useWorkbookProtection;
	/**
	 * 密码
	 */
	private String workbookProtectionPassword;
	/**
	 * 窗口锁定标志
	 */
	private boolean lockWindowsIfProtected;
	/**
	 * 结构保护标志
	 */
	private boolean lockStructureIfProtected;
	/**
	 * 保存时是否格式化xml的标志 
	 */
	private static boolean xmlFormatIndent = false;

	public int getSelectedWorksheet() {
		return selectedWorksheet;
	}

	public Worksheet getCurrentWorksheet() {
		return currentWorksheet;
	}

	public List<Worksheet> getWorksheets() {
		return worksheets;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String value) {
		fileName = value;
	}

	public List<Style> getStyles() {
		return styles;
	}

	public Metadata getWorkbookMetadata() {
		return workbookMetadata;
	}

	public void setWorkbookMetadata(Metadata value) {
		workbookMetadata = value;
	}

	public boolean getUseWorkbookProtection() {
		return useWorkbookProtection;
	}

	public void setUseWorkbookProtection(boolean value) {
		useWorkbookProtection = value;
	}

	public String getWorkbookProtectionPassword() {
		return workbookProtectionPassword;
	}

	public boolean getLockWindowsIfProtected() {
		return lockWindowsIfProtected;
	}

	public boolean getLockStructureIfProtected() {
		return lockStructureIfProtected;
	}
	
	public static boolean getXmlFormatIndent() {
		return xmlFormatIndent;
	}
	
	public static void setXmlFormatIndent(boolean value) {
		xmlFormatIndent = value;
	}

	/**
	 * 构造函数
	 * 
	 * @param createWorkSheet
	 *            为真时创建缺省的工作表
	 * @throws Exception
	 */
	public Workbook(boolean createWorkSheet) throws Exception {
		styles.add(new Style("default"));
		styles.add(BasicStyles.getDottedFill_0_125());
		if (createWorkSheet) addWorksheet("Sheet1");
	}

	public Workbook(String sheetName) throws Exception {
		this(false);
		addWorksheet(sheetName);
	}

	public Workbook(String filename, String sheetName) throws Exception {
		this(false);
		fileName = filename;
		addWorksheet(sheetName);
	}

	/**
	 * 增加工作表
	 */
	public void addWorksheet(String name) throws Exception {
		if (name == null || name.isEmpty())
			throw new Exception("新建的工作表名称不能为空!");
		for (Worksheet item : worksheets)
			if (name.equals(item.getSheetName()))
				throw new Exception("已存在同名工作表[" + name + "].");

		currentWorksheet = new Worksheet(name, worksheets.size() + 1);
		worksheets.add(currentWorksheet);
	}

	/**
	 * 设置当前工作表
	 * 
	 * @param name
	 *            工作表名称
	 * @return 返回设置的工作表
	 */
	public Worksheet setCurrentWorksheet(String name) throws Exception {
		if (name == null || name.isEmpty())
			throw new Exception("设置的工作表名称不能为空!");
		for (Worksheet item : worksheets) {
			if (name.equals(item.getSheetName())) {
				currentWorksheet = item;
				return item;
			}
		}
		
		throw new Exception("工作表[" + name + "]不存在.");
	}

	public Worksheet setCurrentWorksheet(int index) {
		currentWorksheet = worksheets.get(index);
		return currentWorksheet;
	}

	public void setSelectedWorksheet(int worksheetIndex) throws Exception {
		if (worksheetIndex < 0 || worksheetIndex > getWorksheets().size() - 1)
			throw new Exception("工作表索引 " + worksheetIndex + " 超出范围.");
		
		selectedWorksheet = worksheetIndex;
	}

	public void setSelectedWorksheet(Worksheet worksheet) throws Exception {
		if (worksheet == null || worksheet.getSheetName() == null)
			throw new Exception("设置的工作表无效！");
		String wn = worksheet.getSheetName();
		for (int i = 0, n = worksheets.size(); i < n; i++) {
			if (wn == worksheets.get(i).getSheetName()) {
				setSelectedWorksheet(i);
				return;
			}
		}
		
		throw new Exception("工作表不在本工作薄中.");
	}

	public void removeWorksheet(String name) throws Exception {
		int index = 0;
		for (int n = worksheets.size(); index < n; index++)
			if (name == worksheets.get(index).getSheetName()) break;
		
		if (index >= worksheets.size())
			throw new Exception("工作表[" + name + "]不存在.");

		boolean resetCurrent = name == currentWorksheet.getSheetName();
		worksheets.remove(index);

		int wns = worksheets.size();
		if (wns > 0) {
			for (int i = 0; i < wns; i++) {
				worksheets.get(i).setSheetID(i + 1);
				if (resetCurrent && i == 0) currentWorksheet = worksheets.get(i);
			}
		} else currentWorksheet = null;

		if (selectedWorksheet >= wns) selectedWorksheet = wns - 1;
	}

	public void setWorkbookProtection(boolean state, boolean protectWindows,
			boolean protectStructure, String password) {
		lockWindowsIfProtected = protectWindows;
		lockStructureIfProtected = protectStructure;
		workbookProtectionPassword = password;
		useWorkbookProtection = protectWindows || protectStructure ? state : false;
	}

	/**
	 * 增加样式
	 * 
	 * @param style 需要增加的样式
	 * @param distinct 如果为真，则新样式将覆盖已有同样式的identical. 否则，发现同样样式时抛出异常
	 * @return 返回增加的样式
	 */
	public Style addStyle(Style style, boolean distinct) {
		String sn = style.getName();
		boolean styleExits = false;
		Style s = null;
		for (int i = 0, n = styles.size(); i < n; i++) {
			Style item = styles.get(i);
			if (sn.equals(item.getName())) {
				if (item.equals(style) && distinct) s = item;
				styleExits = true;
				break;
			}
		}

		if (styleExits) {
			if (!distinct && s != null)
				throw new RuntimeException("The style with the name '" + sn + "' already exits");
		} else styles.add(style);

		return style;
	}

	/**
	 * 删除样式
	 * 
	 * @param style 要删除的样式
	 */
	public void removeStyle(Style style) throws Exception {
		removeStyle(style, false);
	}

	public void removeStyle(String styleName) throws Exception {
		removeStyle(styleName, false);
	}

	/**
	 * 删除在工作表中定义的样式
	 * 
	 * @param style 要删除的样式
	 * @param onlyIfUnused 如果为真，则只当未被使用时才会被删除
	 */
	public void removeStyle(Style style, boolean onlyIfUnused) throws Exception {
		if (style == null)
			throw new Exception("The style to remove is not defined");
		removeStyle(style.getName(), false);
	}

	public void removeStyle(String styleName, boolean onlyIfUnused) throws Exception {
		if (styleName == null || styleName.isEmpty())
			throw new Exception("The style to remove is not defined (no name specified)");

		int index = 0;
		for (int n = styles.size(); index < n; index++) {
			if (styleName.equals(styles.get(index).getName())) break;
		}

		if (index >= styles.size())
			throw new Exception("The style with the name '" + styleName
					+ "' to remove was not found in the list of styles");
		else if (index == 0 || "default".equals(styles.get(index).getName()))
			throw new Exception("The default style can not be removed");
		else {
			if (onlyIfUnused) {
				boolean styleInUse = false;
				for (Worksheet sheet : getWorksheets()) {
					for (Map.Entry<String, Cell> cell : sheet.getCells().entrySet()) {
						if (styleName.equals(cell.getValue().cellStyle.getName())) {
							styleInUse = true;
							break;
						}
					}
					if (styleInUse) break;
				}
				if (!styleInUse) styles.remove(index);
			}
			else styles.remove(index);
		}
	}

	static class OrganizeStyles {
		List<Style.Border> borders = new ArrayList<Style.Border>();
		List<Style.Fill> fills = new ArrayList<Style.Fill>();
		List<Style.Font> fonts = new ArrayList<Style.Font>();
		List<Style.NumberFormat> numberFormats = new ArrayList<Style.NumberFormat>();
		List<Style.CellXf> cellXfs = new ArrayList<Style.CellXf>();
	};

	/**
	 * 在保存工作薄前重组样式表
	 * @throws Exception 
	 */
	public OrganizeStyles reorganizeStyles() throws Exception {
		OrganizeStyles ret = new OrganizeStyles();
		Style dateStyle = addStyle(Style.BasicStyles.getDateFormat(), true);
		boolean exists = false;
		int existsIndex = 0;
		int customNumberFormat = Style.NumberFormat.CUSTOMFORMAT_START_NUMBER;
		for (int i = 0, n = styles.size(); i < n; i++) {
			Style style = styles.get(i);
			style.internalID = i;

			exists = false;
			for (Style.Border item : ret.borders) {
				if (item.equals(style.currentBorder)) {
					exists = true;
					existsIndex = item.internalID;
					break;
				}
			}
			if (exists)
				style.currentBorder.internalID = existsIndex;
			else {
				style.currentBorder.internalID = ret.borders.size();
				ret.borders.add(style.currentBorder);
			}

			exists = false;
			for (Style.Fill item : ret.fills) {
				if (item.equals(style.currentFill)) {
					exists = true;
					existsIndex = item.internalID;
					break;
				}
			}
			if (exists)
				style.currentFill.internalID = existsIndex;
			else {
				style.currentFill.internalID = ret.fills.size();
				ret.fills.add(style.currentFill);
			}

			exists = false;
			for (Style.Font item : ret.fonts) {
				if (item.equals(style.currentFont)) {
					exists = true;
					existsIndex = item.internalID;
					break;
				}
			}
			if (exists)
				style.currentFont.internalID = existsIndex;
			else {
				style.currentFont.internalID = ret.fonts.size();
				ret.fonts.add(style.currentFont);
			}

			exists = false;
			for (Style.NumberFormat item : ret.numberFormats) {
				if (item.equals(style.currentNumberFormat)) {
					exists = true;
					existsIndex = item.internalID;
					break;
				}
			}
			if (exists)
				style.currentNumberFormat.internalID = existsIndex;
			else {
				style.currentNumberFormat.internalID =
						ret.numberFormats.size();
				ret.numberFormats.add(style.currentNumberFormat);
			}
			if (style.currentNumberFormat.isCustomFormat())
				style.currentNumberFormat.customFormatID = customNumberFormat++;

			exists = false;
			for (Style.CellXf item : ret.cellXfs) {
				if (item.equals(style.currentCellXf)) {
					exists = true;
					existsIndex = item.internalID;
					break;
				}
			}
			if (exists)
				style.currentCellXf.internalID = existsIndex;
			else {
				style.currentCellXf.internalID = ret.cellXfs.size();
				ret.cellXfs.add(style.currentCellXf);
			}
		}

		for (Worksheet sheet : worksheets) {
			for (Map.Entry<String, Cell> item : sheet.getCells().entrySet()) {
				if (item.getValue().fieldtype == Cell.CellType.DATE) {
					if (item.getValue().cellStyle == null)
						item.getValue().setStyle(dateStyle, this);
				}
			}
		}

		Collections.sort(styles);
		Collections.sort(ret.borders);
		Collections.sort(ret.fills);
		Collections.sort(ret.fonts);
		Collections.sort(ret.numberFormats);
		Collections.sort(ret.cellXfs);

		return ret;
	}

	/**
	 * 解决合并单元格的不连续状态
	 */
	public void resolveMergedCells() throws Exception {
		int pos;
		List<Cell.Address> addresses = new ArrayList<Cell.Address>();
		Cell cell;
		for (Worksheet sheet : getWorksheets()) {
			for (Map.Entry<String, Cell.Range> range : sheet.getMergedCells().entrySet()) {
				Cell.Range r = range.getValue();
				pos = 0;
				addresses = Cell.getCellRange(r.startAddress, r.endAddress);
				for (Cell.Address address : addresses) {
					cell = sheet.getCells().get(address.toString());
					if (cell == null) {
						cell = new Cell();
						cell.fieldtype = Cell.CellType.EMPTY;
						cell.rowAddress = address.rowIndex;
						cell.columnAddress = address.columnIndex;
						sheet.addCell(cell);
					}

					if (pos != 0) cell.fieldtype = Cell.CellType.EMPTY;
					pos++;
				}
			}
		}
	}

	public void write(OutputStream stream) throws Exception {
		// LowLevel l = new LowLevel(this).save(stream);
		new FastLowLevel().save(this, stream);
	}

	public void save() throws Exception {
		new FastLowLevel().save(this);
		// LowLevel l = new LowLevel(this).save();
	}

	public void saveAs(String filename) throws Exception {
		String backup = getFileName();
		setFileName(filename);
		new FastLowLevel().save(this);
		// new LowLevel(this).save();
		setFileName(backup);
	}

}
